/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package cn.bbstone.pisces2.server;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.bbstone.pisces2.comm.Const;
import cn.bbstone.pisces2.comm.cache.ServerFliIndexCache;
import cn.bbstone.pisces2.config.ConfigFactory;
import cn.bbstone.pisces2.config.ConfigModel;
import cn.bbstone.pisces2.listener.BaseListenerRegister;
import cn.bbstone.pisces2.listener.IListenerRegister;
import cn.bbstone.pisces2.listener.OpEnum;
import cn.bbstone.pisces2.listener.OpUtil;
import cn.bbstone.pisces2.server.base.ServerCmdRegister;
import cn.bbstone.pisces2.server.fli.FliBuilder;
import cn.bbstone.pisces2.util.CtxUtil;
import cn.hutool.core.collection.CollUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * Server that accept the path of a file an echo back its content.
 */
public final class FileServer {
	private static Logger log = LoggerFactory.getLogger(FileServer.class);

	EventLoopGroup bossGroup, workerGroup;
	public void startup(ConfigModel configModel, IListenerRegister listenerRegister) {
		// init configs
		if (configModel == null) {
			if (CtxUtil.getConfigModel() == null) {
				configModel = ConfigFactory.loadConfigs(Const.SERVER);
			} else {
				configModel = CtxUtil.getConfigModel();
			}
		}
		CtxUtil.setConfigModel(configModel);

		// register OpListners
		BaseListenerRegister.init(listenerRegister);

		// Configure the server.
		bossGroup = new NioEventLoopGroup(1);
		workerGroup = new NioEventLoopGroup();
		try {
			ServerCmdRegister.init();

			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
					.handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						public void initChannel(SocketChannel ch) throws Exception {
							ChannelPipeline p = ch.pipeline();
							// ---- server outbound

							// ---- server inbound
							/**
							 * @param maxFrameLength      解码时，处理每个帧数据的最大长度
							 * @param lengthFieldOffset   该帧数据中，存放该帧数据的长度的数据的起始位置
							 * @param lengthFieldLength   记录该帧数据长度的字段本身的长度
							 * @param lengthAdjustment    修改帧数据长度字段中定义的值，可以为负数
							 * @param initialBytesToStrip 解析的时候需要跳过的字节数
							 *
							 *                            maxFrameLength, 1M bytes
							 *
							 */
							p.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024, 56, 2, 0, 0));
							// server customize handler
							p.addLast(new FileServerHandler());

						}
					});

			// Start the server.
			ChannelFuture f = b.bind(CtxUtil.getConfigModel().getServerPort()).sync();
			f.addListener((ChannelFutureListener) future -> {
				buildServerIndexSp();
				OpUtil.execListener(OpEnum.s4_done_startup_server);
			});
			log.info("server is running...");
			// Wait until the server socket is closed.
			f.channel().closeFuture().sync(); // thread blocked
		} catch (Exception e) {
			log.error("server startup error.", e);
		} finally {
			// Shut down all event loops to terminate all threads.
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}

	/**
	 * shutdown event executors
	 * 
	 */
	public void shutdown() {
		// Shut down all event loops to terminate all threads.
		bossGroup.shutdownGracefully();
		workerGroup.shutdownGracefully();
		// Wait until all threads are terminated.
		bossGroup.terminationFuture().syncUninterruptibly();
		workerGroup.terminationFuture().syncUninterruptibly();

		try {
			// ensure that shutdown has actually completed and won't
			// cause class loader error if JVM starts unloading classes
			Thread.sleep(2);
		} catch (InterruptedException ignore) {
			// ignore
		}
		OpUtil.execListener(OpEnum.s99_server_shutdown);
		log.info("server is shutdown.");
	}
	
	private void buildServerIndexSp() {
		// TODO build buildServerFliIndex & notify ui to load data 
   	 // build fli.idx file and send this file info to client
       List<String> filterList = CollUtil.toList(CtxUtil.getConfigModel().getFilter());
       int totalLines = FliBuilder.buildServerFliIndex(CtxUtil.getConfigModel().getServerRoot(), filterList);
       ServerFliIndexCache.init(Const.SERVER);
       log.debug("File index list size: {}", totalLines);
	}
}
